#include "memory/MemoryBlock.h"

namespace chelper {
namespace memory {

char *MemoryBlock::malloc(uint32_t size_t) {
    assert(size_t <= kChunkSize);

    size_t = align(size_t, 8);

    if (free_list_->empty()) {
        return __malloc(size_t);
    }

    MemoryChunk *chunk = free_list_->pop(size_t);
    if (chunk == nullptr) {
        return __malloc(size_t);
    }

    if (chunk->offset == kChunkSize) {
        free_list_->remove(chunk);
    }
    used_list_.insert({(uint64_t)data_ - (uint64_t)chunk, size_t});
    return chunk->start + chunk->offset - size_t;
}

void MemoryBlock::free(char *buff) {
    ChunkId id = (uint64_t)buff - (uint64_t)data_;
    assert(id <= total_);
    auto it = used_list_.find(id);
    if (it == used_list_.end()) {
        return;
    }
    uint32_t size = it->second;
    uint32_t num = id / (sizeof(MemoryChunk) + kChunkSize);
    MemoryChunk *chunk = (MemoryChunk *)(data_ + num * (sizeof(MemoryChunk) + kChunkSize));
    chunk->used -= size;
    if (chunk->used == 0) {
        chunk->offset = 0;
        free_list_->remove(chunk);
        free_list_->push_front(chunk);
    }
}

void MemoryBlock::allocateMemory(uint64_t size_t) {
    if (free_list_ != nullptr) {
        return;
    }
    free_list_ = new MemoryList();
    total_ = size_t;
    __allocate(size_t);
}

void MemoryBlock::__allocate(uint64_t size_t) {
    uint32_t num = size_t / kChunkSize + 1;
    uint64_t real_size = num * sizeof(MemoryChunk) + size_t;

    data_ = (char *)std::malloc(real_size);
    assert(data_ != nullptr);
}

char *MemoryBlock::__malloc(uint64_t size_t) {
    if (offset_ + size_t > total_) {
        return nullptr;
    }

    char *buff = data_ + offset_;
    offset_ += sizeof(MemoryChunk) + kChunkSize;
    MemoryChunk *chunk = (MemoryChunk *)buff;
    chunk->start = buff + sizeof(MemoryChunk);
    chunk->offset = size_t;
    chunk->used = size_t;
    free_list_->push_back(chunk);
    used_list_.insert({(uint64_t)chunk - (uint64_t)data_, size_t});

    return chunk->start;
}

}  // namespace memory
}  // namespace chelper